﻿using System;
using System.Globalization;
using LukasKopenec.LocalizationSupport;
using Notoric.Commanding.CommandBase;

namespace Notoric.ViewModel
{
    /// <summary>
    /// Abstract base class for resizable view-model elements.
    /// </summary>
    public abstract class ResizableElement : ViewModelElement
    {
        #region Constructors

        /// <summary>
        /// Creates a new <see cref="ResizableElement"/> instance.
        /// </summary>
        /// <param name="adaptedElement">
        /// <para>
        /// Type: <see cref="Model.Elements.ModelElement"/>
        /// </para>
        /// <param name="owner">
        /// <para>
        /// Type: <see cref="ViewModelElement"/>
        /// </para>
        /// <para>
        /// The element that directly owns this element.
        /// </para>
        /// </param>
        /// <para>
        /// The adapted model element instance.
        /// </para>
        /// </param>
        /// <param name="undoStack">
        /// <para>
        /// Type: <see cref="CommandStack"/>
        /// </para>
        /// <para>
        /// The undo stack for this element instance.
        /// </para>
        /// </param>
        /// <param name="redoStack">
        /// <para>
        /// Type: <see cref="CommandStack"/>
        /// </para>
        /// <para>
        /// The redo stack for this element instance.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="adaptedElement"/>, <paramref name="undoStack"/> or <paramref name="redoStack"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="ArgumentException">
        /// <paramref name="adaptedElement"/> does not implement <see cref="Model.Elements.IResizableElement"/> interface.
        /// </exception>
        protected ResizableElement(Model.Elements.ModelElement adaptedElement, ViewModelElement owner, CommandStack undoStack, CommandStack redoStack)
            : base(adaptedElement, owner, undoStack, redoStack)
        {
            if (!(adaptedElement is Model.Elements.IResizableElement))
                throw new ArgumentException("The element does not implement IResizableElement interface!", "adaptedElement");
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Creates an undoable command that sets the height of this element.
        /// </summary>
        /// <param name="height">
        /// <para>
        /// Type: <see cref="Double"/>
        /// </para>
        /// <para>
        /// The new element's height.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="UndoableCommandBase"/>
        /// </para>
        /// <para>
        /// The created command ready for execution.
        /// </para>
        /// </returns>
        public UndoableCommandBase CreateSetHeightCommand(double height)
        {
            height = (double)LengthConverter.ConvertBack(height, typeof(double), null, CultureInfo.CurrentCulture);

            return new SetterCommand<double>(UndoStack, RedoStack)
            {
                Target = this,
                Property = "Height",
                Value = height,
                Name = (string)LocalizationManager.GetValue("Commands.Names.SetSize")
            };
        }

        /// <summary>
        /// Creates an undoable command that sets the size of this element.
        /// </summary>
        /// <param name="width">
        /// <para>
        /// Type: <see cref="Double"/>
        /// </para>
        /// <para>
        /// The value of the <see cref="Width"/> property.
        /// </para>
        /// </param>
        /// <param name="height">
        /// <para>
        /// Type: <see cref="Double"/>
        /// </para>
        /// <para>
        /// The value of the <see cref="Height"/> property.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="UndoableCommandBase"/>
        /// </para>
        /// <para>
        /// The created command ready for execution.
        /// </para>
        /// </returns>
        public UndoableCommandBase CreateSetSizeCommand(double width, double height)
        {
            width = (double)LengthConverter.ConvertBack(width, typeof(double), null, CultureInfo.CurrentCulture);
            height = (double)LengthConverter.ConvertBack(height, typeof(double), null, CultureInfo.CurrentCulture);

            return new SetterCommand<double, double>(UndoStack, RedoStack)
            {
                Target = this,
                Property1 = "Width",
                Property2 = "Height",
                Value1 = width,
                Value2 = height,
                Name = (string)LocalizationManager.GetValue("Commands.Names.SetSize")
            };
        }

        /// <summary>
        /// Creates an undoable command that sets the width of this element.
        /// </summary>
        /// <param name="width">
        /// <para>
        /// Type: <see cref="Double"/>
        /// </para>
        /// <para>
        /// The new element's width.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="UndoableCommandBase"/>
        /// </para>
        /// <para>
        /// The created command ready for execution.
        /// </para>
        /// </returns>
        public UndoableCommandBase CreateSetWidthCommand(double width)
        {
            width = (double)LengthConverter.ConvertBack(width, typeof(double), null, CultureInfo.CurrentCulture);

            return new SetterCommand<double>(UndoStack, RedoStack)
            {
                Target = this,
                Property = "Width",
                Value = width,
                Name = (string)LocalizationManager.GetValue("Commands.Names.SetSize")
            };
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets or sets the height of the element.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Double"/>
        /// </para>
        /// <para>
        /// The height (in device independent pixels) of the element.
        /// </para>
        /// </value>
        public double Height
        {
            get 
            {
                double height;

                if (UnsavedChanges.ContainsKey("Height"))
                {
                    var cmd = (SetterCommand<double>)UnsavedChanges["Height"];
                    height = cmd.Value;
                }
                else
                    height = ((Model.Elements.IResizableElement)AdaptedElement).Height;

                return (double)LengthConverter.Convert(height, typeof(double), null, CultureInfo.CurrentCulture);
            }
            set 
            {
                if (UnsavedChanges.ContainsKey("Height"))
                {
                    var cmd = (SetterCommand<double>)UnsavedChanges["Height"];
                    cmd.Value = (double)LengthConverter.ConvertBack(value, typeof(double), null, CultureInfo.CurrentCulture);
                }
                else
                    UnsavedChanges["Height"] = CreateSetHeightCommand(value);

                RaisePropertyChanged("Height");
            }
        }

        /// <summary>
        /// Gets or sets the width of the element.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Double"/>
        /// </para>
        /// <para>
        /// The width (in device independent pixels) of the element.
        /// </para>
        /// </value>
        public double Width
        {
            get
            {
                double width;

                if (UnsavedChanges.ContainsKey("Width"))
                {
                    var cmd = (SetterCommand<double>)UnsavedChanges["Width"];
                    width = cmd.Value;
                }
                else
                    width = ((Model.Elements.IResizableElement)AdaptedElement).Width;

                return (double)LengthConverter.Convert(width, typeof(double), null, CultureInfo.CurrentCulture);
            }
            set
            {
                if (UnsavedChanges.ContainsKey("Width"))
                {
                    var cmd = (SetterCommand<double>)UnsavedChanges["Width"];
                    cmd.Value = (double)LengthConverter.ConvertBack(value, typeof(double), null, CultureInfo.CurrentCulture);
                }
                else
                    UnsavedChanges["Width"] = CreateSetWidthCommand(value);

                RaisePropertyChanged("Width");
            }
        }

        #endregion
    }
}
